package com.cxs.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cxs.dto.FileUploadDTO;
import com.cxs.mapper.SysChunkRecordMapper;
import com.cxs.mapper.SysFileMapper;
import com.cxs.model.SysChunkRecord;
import com.cxs.model.SysFile;
import com.cxs.service.FileUploadService;
import com.cxs.utils.FileUploadUtil;
import com.cxs.utils.HttpStatus;
import com.cxs.utils.MsgConstant;
import com.cxs.utils.Result;
import com.cxs.vo.UploadResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.Cleaner;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.time.LocalDateTime;

/*
 * @Project:file-upload-senior
 * @Author:cxs
 * @Motto:放下杂念,只为迎接明天更好的自己
 * */
@Slf4j
@Service
public class FileUploadServiceImpl implements FileUploadService {

    @Autowired
    private SysFileMapper sysFileMapper;

    @Autowired
    private SysChunkRecordMapper sysChunkRecordMapper;

    @Autowired
    private FileUploadUtil fileUploadUtil;


    @Override
    public void chunkFileUpload(FileUploadDTO dto, Result result) {
        UploadResultVo vo = new UploadResultVo();
        // 检测文件是否存在,大文件秒传
        SysFile sysFile = queryMd5(dto.getFileMd5());
        if (null != sysFile) {
            vo.setFileKid(sysFile.getKid()).setUploaded(Boolean.TRUE);
            result.setData(vo);
            return;
        }
        // 检测分片是否存在
        SysChunkRecord scr = queryChunkMd5(dto.getCurrentChunkMd5());
        if (scr != null) {
            vo.setFileKid(scr.getKid()).setUploaded(Boolean.TRUE);
            result.setData(vo);
            return;
        }
        try {
            MultipartFile multipartFile = dto.getFile();
            String filePath = fileUploadUtil.getFilePath();
            File file = new File(filePath, dto.getName());

            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
            if (randomAccessFile.length() == 0l) {
                randomAccessFile.setLength(dto.getSize());
            }
            // 计算分片文件的位置
            int pos = dto.getCurrentChunk() * dto.getChunkSize();
            FileChannel channel = randomAccessFile.getChannel();
            MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, pos, multipartFile.getSize());
            map.put(multipartFile.getBytes());
            cleanBuffer(map);
            channel.close();
            randomAccessFile.close();
            // 存分片数据
            String chunkKid = saveSysChunkRecord(file, dto);
            vo.setChunkKid(chunkKid).setUploaded(Boolean.TRUE);
            if (dto.getCurrentChunk() == dto.getChunks() - 1) {
                LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(SysChunkRecord::getFileMd5, dto.getFileMd5());
                Integer integer = sysChunkRecordMapper.selectCount(wrapper);
                int flag = 0;
                while (integer != dto.getChunks() && flag < 10) {
                    Thread.sleep(100);
                    integer = sysChunkRecordMapper.selectCount(wrapper);
                    flag++;
                }
                if(integer == dto.getChunks()) {
                    // 存文件
                    SysFile fileInfo = buildSysFile(dto, file);
                    int insert = sysFileMapper.insert(fileInfo);
                    if (insert == 1) {
                        // 清除分片数据
                        cleanChunkData(dto.getFileMd5());
                    }
                    vo.setFileKid(fileInfo.getKid()).setUploaded(Boolean.TRUE);
                } else {
                    // 清除分片数据
                    cleanChunkData(dto.getFileMd5());
                    // 文件上传失败
                    result.setCode(HttpStatus.ERROR);
                    result.setMsg("文件上传失败");
                    return;
                }
            }
            result.setData(vo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据md5值清除分片数据
     */
    private void cleanChunkData(String md5) {
        LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<SysChunkRecord>()
                .eq(SysChunkRecord::getFileMd5, md5);
        sysChunkRecordMapper.delete(wrapper);
    }

    private String saveSysChunkRecord(File file, FileUploadDTO param) {
        SysChunkRecord sysChunkRecord = new SysChunkRecord()
                .setChunkFileName(file.getName())
                .setChunkFilePath(file.getAbsolutePath())
                .setFileMd5(param.getFileMd5())
                .setCurrentChunkMd5(param.getCurrentChunkMd5())
                .setChunks(param.getChunks())
                .setChunkSize(param.getChunkSize())
                .setCreateTime(LocalDateTime.now())
                .setCurrentChunk(param.getCurrentChunk());
        sysChunkRecordMapper.insert(sysChunkRecord);
        return sysChunkRecord.getKid();
    }

    /**
     * 关闭map
     * @param map
     */
    private void cleanBuffer(MappedByteBuffer map) {
        try {
            Method getCleanerMethod = map.getClass().getMethod("cleaner");
            Cleaner.create(map, null);
            getCleanerMethod.setAccessible(true);
            Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(map);
            cleaner.clean();
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检测当前分片的md5
     * @param currentChunkMd5
     * @return
     */
    private SysChunkRecord queryChunkMd5(String currentChunkMd5) {
        LambdaQueryWrapper<SysChunkRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysChunkRecord::getCurrentChunkMd5, currentChunkMd5);
        return sysChunkRecordMapper.selectOne(wrapper);
    }

    @Override
    public void singleFileUpload(FileUploadDTO dto, Result result) {
        try {
            UploadResultVo vo = new UploadResultVo();
            SysFile sysFile = queryMd5(dto.getFileMd5());
            if (null != sysFile) {
                vo.setFileKid(sysFile.getKid()).setUploaded(Boolean.TRUE);
                result.setData(vo);
                return;
            }
            // 比对md5值
            if (!fileUploadUtil.checkMd5(dto.getFile().getInputStream(), dto.getFileMd5())) {
                result.setCode(HttpStatus.ERROR);
                result.setMsg(MsgConstant.MD5_CHECK_EXCEPTION);
                return;
            }
            // 存文件
            String filePath = fileUploadUtil.getFilePath();
            File file = new File(filePath, dto.getName());
            dto.getFile().transferTo(file);
            // 存数据库
            SysFile fileInfo = buildSysFile(dto, file);
            sysFileMapper.insert(fileInfo);
            vo.setFileKid(fileInfo.getKid()).setUploaded(Boolean.TRUE);
            result.setData(vo);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建SysFile
     * @param dto
     * @param file
     * @return
     */
    private SysFile buildSysFile(FileUploadDTO dto, File file) {
        return SysFile.builder()
                .fileMd5(dto.getFileMd5())
                .fileName(dto.getName())
                .filePath(file.getAbsolutePath())
                .fileType(dto.getType())
                .fileSize(dto.getSize())
                .createTime(LocalDateTime.now())
                .extension(dto.getName().substring(dto.getName().lastIndexOf(".")))
                .build();
    }

    /**
     * 根据md5查询
     * @param fileMd5
     * @return
     */
    private SysFile queryMd5(String fileMd5) {
        LambdaQueryWrapper<SysFile> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysFile::getFileMd5, fileMd5);
        return sysFileMapper.selectOne(wrapper);
    }
}
